package com.tiancheng.onecode.configuration.auth;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.tiancheng.onecode.utils.JsonUtil;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * [Sa-Token 权限认证] 配置类
 *
 * @author click33
 */
@Configuration
public class SaTokenConfigure {
    @Resource
    private IgnoreUrlsConfig ignoreUrlsConfig;
    /**
     * Redis缓存权限规则（路径->资源）
     */
    String PATH_RESOURCE_MAP = "auth:pathResourceMap";
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 注册Sa-Token全局过滤器
     */
    @Bean
    public SaServletFilter getSaReactorFilter() {
        return new SaServletFilter()
                // 指定 拦截路由 与 放行路由
                .addInclude("/**").setExcludeList(ignoreUrlsConfig.getUrls())    /* 排除掉 /favicon.ico */
                // 鉴权方法：每次访问进入
                .setAuth(obj -> {
                    // 对于OPTIONS预检请求直接放行
                    SaRouter.match(SaHttpMethod.OPTIONS).stop();
                    SaRouter
                            .notMatch("/api/login")        // 排除掉的 path 列表，可以写多个
                            .match("/api/**")    // 拦截的 path 列表，可以写多个 */
                            .check(r -> StpKit.mini.checkLogin()).stop();        // 要执行的校验动作，可以写完整的 lambda 表达式
                    SaRouter
                            .notMatch("/admin/login", "/admin/register")        // 排除掉的 path 列表，可以写多个
                            .match("/admin/**")    // 拦截的 path 列表，可以写多个 */
                            .check(r -> StpKit.admin.checkLogin()).stop();        // 要执行的校验动作，可以写完整的 lambda 表达式
                    SaRouter.match("/**", r -> StpKit.admin.checkLogin());
                    // 权限认证：管理后台用户权限校验
                    // 获取Redis中缓存的各个接口路径所需权限规则
                    Map<Object, Object> pathResourceMap = redisTemplate.opsForHash().entries(PATH_RESOURCE_MAP);
                    // 获取到访问当前接口所需权限（一个路径对应多个资源时，拥有任意一个资源都可以访问该路径）
                    List<String> needPermissionList = new ArrayList<>();
                    // 获取当前请求路径
                    String requestPath = SaHolder.getRequest().getRequestPath();
                    // 创建路径匹配器
                    PathMatcher pathMatcher = new AntPathMatcher();
                    Set<Map.Entry<Object, Object>> entrySet = pathResourceMap.entrySet();
                    for (Map.Entry<Object, Object> entry : entrySet) {
                        String pattern = (String) entry.getKey();
                        if (pathMatcher.match(pattern, requestPath)) {
                            needPermissionList.add((String) entry.getValue());
                        }
                    }
                    // 接口需要权限时鉴权
                    if (CollUtil.isNotEmpty(needPermissionList)) {
                        SaRouter.match(requestPath, r -> StpUtil.checkPermissionOr(Convert.toStrArray(needPermissionList)));
                    }
                })
                // setAuth方法异常处理
                .setError(this::handleException);
    }

    /**
     * 自定义异常处理
     *
     * @param e
     * @return
     */
    private String handleException(Throwable e) {
        //设置错误返回格式为JSON
        if (e instanceof NotLoginException) {
            return JsonUtil.toJsonString(AuthExceptionResult.fail(403, "未登录"));
        } else if (e instanceof NotPermissionException) {
            return JsonUtil.toJsonString(AuthExceptionResult.fail(405, "无权限操作"));
        } else {
            return JsonUtil.toJsonString(AuthExceptionResult.fail(406, "授权异常"));
        }
    }

}
